#!/bin/bash
#
#   __     ___    _  ____  _____  
#   \ \   / / |  | |/ __ \|  __ \ 
#    \ \_/ /| |__| | |  | | |__) |
#     \   / |  __  | |  | |  ___/ 
#      | |  | |  | | |__| | |     
#      |_|  |_|  |_|\____/|_|     
#   :: YiHui Operations Startup Script ::
#

[[ -n "$DEBUG" ]] && set -x
# Initialize variables 
WORKING_DIR="$(pwd)"

# Initialize variables by .env
export_envs() {
	while IFS='=' read -r key temp || [ -n "$key" ]; do
        case "$key" in
        \#*)
            continue
            ;;
        '')
            continue
            ;;
        *)
            value=$(eval echo "$temp")
            eval export "$key='$value'";
            ;;
        esac
		
	done < $1
}

[[ -n "$DOTENV_FILE" ]] && dotenvfile="$DOTENV_FILE"
[[ -n "$dotenvfile" ]] || dotenvfile=".env"
# Follow symlinks to find the real script file and detect init.d script
cd "$(dirname "$0")" || exit 1
[[ -z "$scriptfile" ]] && scriptfile=$(pwd)/$(basename "$0")
while [[ -L "$scriptfile" ]]; do
  if [[ "$scriptfile" =~ init\.d ]]; then
    init_script=$(basename "$scriptfile")
  else
    configfile="${scriptfile%.*}.conf"
    # shellcheck source=/dev/null
    [[ -r ${configfile} ]] && source "${configfile}"
  fi
  scriptfile=$(readlink "$scriptfile")
  cd "$(dirname "$scriptfile")" || exit 1
  scriptfile=$(pwd)/$(basename "$scriptfile")
done
scriptfolder="$( (cd "$(dirname "$scriptfile")" && pwd -P) )"
! [[ "$dotenvfile" == /* ]] && dotenvfile="${scriptfolder}/${dotenvfile}"
[[ -r ${dotenvfile} ]] && export_envs "${dotenvfile}"
cd "$WORKING_DIR" || exit 1

# shellcheck disable=SC2153
[[ -n "$EXECTYPE" ]] && exectype="$EXECTYPE"
[[ -n "$EXEFILE" ]] && exefile="$EXEFILE"
[[ -n "$JARFILE" ]] && jarfile="$JARFILE"
[[ -n "$APP_NAME" ]] && identity="$APP_NAME"
scriptname=$(basename "$0") && scriptname=${scriptname%.*}

# Source any config file
configfile="$(basename "${scriptfile%.*}.conf")"

# Initialize CONF_FOLDER location defaulting to scriptfolder
[[ -z "$CONF_FOLDER" ]] && CONF_FOLDER="${scriptfolder}"
# shellcheck source=/dev/null
[[ -r "${CONF_FOLDER}/${configfile}" ]] && source "${CONF_FOLDER}/${configfile}"

# ANSI Colors
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }

# Initialize PID/LOG locations if they weren't provided by the config file
[[ -z "$PID_FOLDER" ]] && PID_FOLDER="/var/run"
[[ -z "$LOG_FOLDER" ]] && LOG_FOLDER="/var/log"
! [[ "$PID_FOLDER" == /* ]] && PID_FOLDER="$(dirname "$scriptfile")"/"$PID_FOLDER"
! [[ "$LOG_FOLDER" == /* ]] && LOG_FOLDER="$(dirname "$scriptfile")"/"$LOG_FOLDER"
! [[ -x "$PID_FOLDER" ]] && echoYellow "PID_FOLDER $PID_FOLDER does not exist. Falling back to /tmp" && PID_FOLDER="/tmp"
! [[ -x "$LOG_FOLDER" ]] && echoYellow "LOG_FOLDER $LOG_FOLDER does not exist. Falling back to /tmp" && LOG_FOLDER="/tmp"

# Set up defaults
[[ -z "$MODE" ]] && MODE="auto" # modes are "auto", "service" or "run"

# Create an identity for log/pid files
if [[ -z "$identity" ]]; then
  if [[ -n "$init_script" ]]; then
    identity="${init_script}"
  else
    identity=$(basename "${scriptfile%.*}")_${scriptfolder//\//}
  fi
fi
systemd_file="/etc/systemd/system/${identity}.service"
initd_file="/etc/init.d/${identity}"

# Initialize log file name if not provided by the config file
[[ -z "$LOG_FILENAME" ]] && LOG_FILENAME="${identity}.log"

# Initialize stop wait time if not provided by the config file
[[ -z "$STOP_WAIT_TIME" ]] && STOP_WAIT_TIME="60"

# Utility functions
checkPermissions() {
  touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; return 4; }
  touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; return 4; }
}

isRunning() {
  ps -p "$1" &> /dev/null
}

await_file() {
  end=$(date +%s)
  let "end+=10"
  while [[ ! -s "$1" ]]
  do
    now=$(date +%s)
    if [[ $now -ge $end ]]; then
      break
    fi
    sleep 1
  done
}

# Build the pid and log filenames
PID_FOLDER="$PID_FOLDER/${identity}"
pid_file="$PID_FOLDER/${identity}.pid"
log_file="$LOG_FOLDER/$LOG_FILENAME"

# Determine the user to run as if we are root
# shellcheck disable=SC2012
[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$scriptfile" | awk '{print $3}')

# Issue a warning if the application will run as root
[[ $(id -u ${run_user}) == "0" ]] && { echoYellow "Application is running as root (UID 0). This is considered insecure."; }


# Determine the script mode
action="$1"
shift


find_exe() {
    tmpexefile=$1
    if [[ "$#" -eq "2" ]]; then
        [[ -z "$tmpexefile" ]] && tmpexefile="$2"
    fi
    ! [[ "$tmpexefile" == /* ]] && tmpexefile="$scriptfolder"/"$tmpexefile"
    if [[ -f "$tmpexefile" && -x "$tmpexefile" ]];  then
        realexe="$tmpexefile"
        arguments=($RUN_ARGS)
    else
        return 1
    fi
}

find_java() {
    # Find Java
    if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
        realexe="$JAVA_HOME/bin/java"
    elif type -p java > /dev/null 2>&1; then
        realexe=$(type -p java)
    elif [[ -x "/usr/bin/java" ]];  then
        realexe="/usr/bin/java"
    else
        return 1
    fi
}

find_jar() {
    tmpjarfile=$1
    if [[ "$#" -eq "2" ]]; then
        [[ -z "$tmpjarfile" ]] && tmpjarfile="$2.jar"
    fi
    ! [[ "$tmpjarfile" == /* ]] && tmpjarfile="$scriptfolder"/"$tmpjarfile"
    find_java
    if [[ -f "$tmpjarfile" && -x "$realexe" ]]; then
        echo "$@"
        arguments=(-Dsun.misc.URLClassPath.disableJarChecking=true $JAVA_OPTS -jar "$tmpjarfile" $RUN_ARGS )
    else
        return 1
    fi
}

case "$exectype" in
    exe)
        find_exe "$exefile" "$scriptname" || exit 1
    ;;
    jar)
        find_jar "$jarfile" "$scriptname" || exit 1
    ;;
    *)
        find_exe "$exefile" || find_jar "$jarfile"  || find_exe "$exefile" "$scriptname" || find_jar "$jarfile" "$scriptname" || exit 1
    ;;
esac

enable_systemd() {
    [[ -f "$systemd_file" ]] && { echoYellow "Already install service [$identity]"; return 0; }
    do_enable_systemd "$@"
}

do_enable_systemd() {
    cat << EOF > ${systemd_file}
[Unit]
Description=${identity}
[Service]
LimitNOFILE=500000
SuccessExitStatus=SIGALRM
TimeoutStopSec=20
Type=forking
WorkingDirectory=${scriptfolder}
PIDFile=${pid_file}
Environment="MODE=service"
ExecStart=${scriptfile} start
ExecStop=${scriptfile} stop
TimeoutStopSec=10
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
    systemctl daemon-reload
    echoGreen "Installed [$identity]"
}

disable_systemd() {
    [[ -f $systemd_file ]] || { echoYellow "Not installed [$identity]"; return 0; }
    do_disable_systemd "$@"
}

do_disable_systemd(){
    stop "$@"
    rm "$systemd_file"
    systemctl daemon-reload
    echoGreen "Uninstalled [$identity]"
}

enable_initd() {
    [[ -f "$initd_file" ]] && { echoYellow "Already install service [$identity]"; return 0; }
    do_enable_initd "$@"
}

do_enable_initd(){
    [[ -f "$initd_file" ]] || ln -s "$scriptfile" "$initd_file"
}

disable_initd() {
    [[ -f $initd_file ]] || { echoYellow "Not installed [$identity]"; return 0; }
    do_disable_systemd "$@"
}

do_disable_initd(){
    "$initd_file" stop
    rm "$initd_file"
}


start() {
  if [[ -f "$pid_file" ]]; then
    pid=$(cat "$pid_file")
    isRunning "$pid" && { echoYellow "Already running [$pid]"; return 0; }
  fi
  do_start "$@"
}

do_start() {
    working_dir=$(dirname "$scriptfile")
    pushd "$working_dir" > /dev/null
    if [[ ! -e "$PID_FOLDER" ]]; then
        mkdir -p "$PID_FOLDER" &> /dev/null
        if [[ -n "$run_user" ]]; then
        chown "$run_user" "$PID_FOLDER"
        fi
    fi
    if [[ ! -e "$log_file" ]]; then
        touch "$log_file" &> /dev/null
        if [[ -n "$run_user" ]]; then
        chown "$run_user" "$log_file"
        fi
    fi
    if [[ -n "$run_user" ]]; then
        checkPermissions || return $?
        su -s /bin/sh -c "$realexe $(printf "\"%s\" " "${arguments[@]}") >> \"$log_file\" 2>&1 & echo \$!" "$run_user" > "$pid_file"
        pid=$(cat "$pid_file")
    else
        checkPermissions || return $?
        "$realexe" "${arguments[@]}" >> "$log_file" 2>&1 &
        pid=$!
        disown $pid
        echo "$pid" > "$pid_file"
    fi
    [[ -z $pid ]] && { echoRed "Failed to start"; return 1; }
    echoGreen "Started [$pid]"
}

stop() {
  working_dir=$(dirname "$scriptfile")
  pushd "$working_dir" > /dev/null
  [[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
  pid=$(cat "$pid_file")
  isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
  do_stop "$pid" "$pid_file"
}

do_stop() {
  kill "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
  for i in $(seq 1 $STOP_WAIT_TIME); do
    isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
    [[ $i -eq STOP_WAIT_TIME/2 ]] && kill "$1" &> /dev/null
    sleep 1
  done
  echoRed "Unable to kill process $1";
  return 1;
}

force_stop() {
  [[ -f $pid_file ]] || { echoYellow "Not running (pidfile not found)"; return 0; }
  pid=$(cat "$pid_file")
  isRunning "$pid" || { echoYellow "Not running (process ${pid}). Removing stale pid file."; rm -f "$pid_file"; return 0; }
  do_force_stop "$pid" "$pid_file"
}

do_force_stop() {
  kill -9 "$1" &> /dev/null || { echoRed "Unable to kill process $1"; return 1; }
  for i in $(seq 1 $STOP_WAIT_TIME); do
    isRunning "$1" || { echoGreen "Stopped [$1]"; rm -f "$2"; return 0; }
    [[ $i -eq STOP_WAIT_TIME/2 ]] && kill -9 "$1" &> /dev/null
    sleep 1
  done
  echoRed "Unable to kill process $1";
  return 1;
}

restart() {
  stop && start
}

force_reload() {
  working_dir=$(dirname "$scriptfile")
  pushd "$working_dir" > /dev/null
  [[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; return 7; }
  pid=$(cat "$pid_file")
  rm -f "$pid_file"
  isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 7; }
  do_stop "$pid" "$pid_file"
  do_start
}

status() {
  working_dir=$(dirname "$scriptfile")
  pushd "$working_dir" > /dev/null
  [[ -f "$pid_file" ]] || { echoRed "Not running"; return 3; }
  pid=$(cat "$pid_file")
  isRunning "$pid" || { echoRed "Not running (process ${pid} not found)"; return 1; }
  echoGreen "Running [$pid]"
  return 0
}

run() {
    pushd "$(dirname "$scriptfile")" > /dev/null
    "$realexe" "${arguments[@]}"
    result=$?
    popd > /dev/null
    return "$result"
}
# Call the appropriate action function
case "$action" in
enable-systemd)
  enable_systemd "$@"; exit $?;;
disable-systemd)
  disable_systemd "$@"; exit $?;;
enable-initd)
  enable_initd "$@"; exit $?;;
disable-initd)
  disable_initd "$@"; exit $?;;
start)
  start "$@"; exit $?;;
stop)
  stop "$@"; exit $?;;
force-stop)
  force_stop "$@"; exit $?;;
restart)
  restart "$@"; exit $?;;
force-reload)
  force_reload "$@"; exit $?;;
status)
  status "$@"; exit $?;;
run)
  run "$@"; exit $?;;
*)
  echo "Usage: $0 {enable-systemd|disable-systemd|enable-initd|disable-initd|start|stop|force-stop|restart|force-reload|status|run}"; exit 1;
esac

exit 0
